/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "TcpClient.h"
#include <cassert>
#include "UGame/System/Error.h"
#include "SocketApi.h"
#include "TcpDispatcher.h"
#include "TcpClientDelegate.h"

namespace UGame {
namespace Net {

	TcpClient::TcpClient(void)
		: _fd(-1)
		, _bev(NULL)
		, _delegate(NULL)
		, _errorString()
		, _errorCode(0)
		, _fatalError(false)
	{

	}

	TcpClient::~TcpClient(void)
	{
		if (_bev != NULL) {
			bufferevent_free(_bev);
			_bev = NULL;
		}
	}

	void TcpClient::SetDelegate(TcpClientDelegate* _Delegate) {
		_delegate = _Delegate;
		_delegate->_SetPeer(this);
	}

	bool TcpClient::Connect(TcpDispatcher* _Dispatcher, const std::string& _Host, int _Port)
	{
		UASSERT(_fd == -1 && _bev == NULL && _delegate != NULL && _Dispatcher != NULL);
		//_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		//if (_fd == -1) {
		//	return false;
		//}

		_bev = bufferevent_socket_new(_Dispatcher->AcquireBase(), -1, BEV_OPT_CLOSE_ON_FREE);
		if (_bev == NULL) {
			return false;
		}

		bufferevent_setcb(_bev,
			&TcpClient::_ReadCallback,
			&TcpClient::_WriteCallback,
			&TcpClient::_EventCallback,
			this);
		bufferevent_enable(_bev, EV_WRITE);
		bufferevent_enable(_bev, EV_READ);

		return bufferevent_socket_connect_hostname(_bev, 
			NULL, AF_UNSPEC, _Host.c_str(), _Port) == 0;
	}

	void TcpClient::Close(void)
	{
		if (_bev != NULL) {
			bufferevent_free(_bev);
			_bev = NULL;
			_fd = -1;
			_delegate->OnClose();
		}
	}

	void TcpClient::Send(const std::uint8_t *_Data, std::size_t _Size)
	{
		if (!_fatalError)
		{
			bufferevent_enable(_bev, EV_WRITE);
			if (bufferevent_write(_bev, _Data, _Size) != 0) {
				_OnError();
			}
		}
	}

	std::size_t TcpClient::GetReceivedSize(void)
	{
		return evbuffer_get_length(bufferevent_get_input(_bev));
	}

	int TcpClient::Read(std::uint8_t *_Data, std::size_t _Size)
	{
		return evbuffer_remove(bufferevent_get_input(_bev), _Data, _Size);
	}

	int TcpClient::ReadPeek(std::uint8_t *_Data, std::size_t _Size)
	{
		return evbuffer_copyout(bufferevent_get_input(_bev), _Data, _Size);
	}

	std::size_t TcpClient::ReadSkip(std::size_t _Size)
	{
		if (evbuffer_drain(bufferevent_get_input(_bev), _Size) == 0) {
			return _Size;
		}
		return 0;
	}

	void TcpClient::EnableReceive(bool _Enable)
	{
		if (_Enable) {
			bufferevent_enable(_bev, EV_READ);
		}
		else {
			bufferevent_disable(_bev, EV_READ);
		}
	}

	void TcpClient::EnableSend(bool _Enable)
	{
		if (_Enable) {
			bufferevent_enable(_bev, EV_WRITE);
		}
		else {
			bufferevent_disable(_bev, EV_WRITE);
		}
	}

	void TcpClient::_OnError(void)
	{
		if (_fatalError) {
			return;
		}
		_fatalError = true;

		EnableSend(false);
	}

	void TcpClient::_ReadCallback(struct bufferevent *, void *_Userptr)
	{
		TcpClient *_Client = static_cast<TcpClient*>(_Userptr);
		UASSERT(_Client);
		_Client->_delegate->OnRead();
	}

	void TcpClient::_WriteCallback(struct bufferevent *, void *_Userptr)
	{
		TcpClient *_Client = static_cast<TcpClient*>(_Userptr);
		UASSERT(_Client);

		if (_Client->_fatalError)
		{
			_Client->EnableSend(false);
			_Client->_delegate->OnWrite();
		}
	}

	void TcpClient::_EventCallback(struct bufferevent *, short _Events, void *_Userptr)
	{
		TcpClient *_Client = static_cast<TcpClient*>(_Userptr);
		UASSERT(_Client);

		if (_Events & BEV_EVENT_CONNECTED) {
			_Client->_delegate->OnOpen();
		} else if (_Events & BEV_EVENT_EOF) {
			_Client->_OnError();
			_Client->_delegate->OnEof();
		}
		else if (_Events & BEV_EVENT_ERROR) {
			_Client->_errorCode = System::GetLastNetError();
			System::GetErrorString(_Client->_errorString, _Client->_errorCode);
			_Client->_OnError();
			_Client->_delegate->OnError();
		}
		else if (_Events & BEV_EVENT_TIMEOUT) {
			int _Flags = 0;
			if (_Events & BEV_EVENT_READING) {
				_Flags |= EF_READ;
			}
			if (_Events & BEV_EVENT_WRITING) {
				_Flags |= EF_WRITE;
			}
			_Client->_delegate->OnTimeout(_Flags);
		}
	}
}}